#include "TFLJourneyPlannerQuery.h"
#include <Binder.h>
#include "CharUtils.h"
#include <ezgzip.h>
#include <FunTrace.h>
#include <eikenv.h>
#include <eikappui.h>
#include <eikapp.h>
#include <SingaporeBusDocument.h>

const TInt DndTimedOut = -5120;


const TInt kTFLJourneyPlannerTimeout = 45*1000000;


static TPtrC8 IrisExtractField(const TDesC8& aPrefix, const TDesC8& aSuffix, TPtrC8& aBuf)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	TPtrC8 ret;
	TInt begfield = aBuf.Find(aPrefix);
	if(begfield != KErrNotFound)
		{
		TInt endfield = aBuf.Mid(begfield + aPrefix.Length()).Find(aSuffix);
		if(endfield != KErrNotFound)
			{
			ret.Set( aBuf.Mid(begfield + aPrefix.Length(), endfield) );
			aBuf.Set( aBuf.Mid( begfield + aPrefix.Length() + endfield + aSuffix.Length() ) );
			return ret;
			}
		}
	aBuf.Set( aBuf.Mid( aBuf.Length() ) );
	return ret;
	}



CTFLJourneyPlannerQuery::CTFLJourneyPlannerQuery() : iStopOrigin(NULL), iStopDestination(NULL)
	{
	iTFLJourneyPlannerTimeoutTimer.CreateLocal();	
	}

CTFLJourneyPlannerQuery::~CTFLJourneyPlannerQuery()
	{
	Cancel();
	delete iTFLJourneyPlannerTimeoutTarget.Unmanage();
	iTFLJourneyPlannerTimeoutTimer.Close();
	iHTTPSession.Close();
	delete iStopOrigin;
	delete iStopDestination;
	}

CTFLJourneyPlannerQuery* CTFLJourneyPlannerQuery::NewL()
	{
TFunTrace funTrace(__PRETTY_FUNCTION__);
CTFLJourneyPlannerQuery* self = CTFLJourneyPlannerQuery::NewLC();
	CleanupStack::Pop( self );
	return self;
	}

CTFLJourneyPlannerQuery* CTFLJourneyPlannerQuery::NewLC()
	{
TFunTrace funTrace(__PRETTY_FUNCTION__);
	CTFLJourneyPlannerQuery* self = new ( ELeave ) CTFLJourneyPlannerQuery();
	CleanupStack::PushL( self );
	self->ConstructL();
	return self;
	}

void CTFLJourneyPlannerQuery::ConstructL()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iTFLJourneyPlannerTimeoutTarget = new (ELeave) scalp::CActiveTarget(
			scalp::MakeFunctionL ( scalp::Bind(&RTimer::Cancel, &iTFLJourneyPlannerTimeoutTimer) )
			);
	iTFLJourneyPlannerTimeoutTarget->AppendRunL(
			scalp::MakeFunctionL ( scalp::Bind(&CTFLJourneyPlannerQuery::TFLJourneyPlannerTransactionTimeoutL, this) )
			);
	iTFLJourneyPlannerTransactionTarget = CHTTPTransactionTarget::NewL();
	iTFLJourneyPlannerTransactionTarget->SetRun( scalp::MakeFunctionL( scalp::Bind(&CTFLJourneyPlannerQuery::ReceivedTFLJourneyPlannerAnswerL, this, scalp::Free1, scalp::Free2, scalp::Free3, scalp::Free4) ) );
	iTFLJourneyPlannerTransactionTarget->SetRunError( scalp::MakeFunctionL( scalp::Bind(&CTFLJourneyPlannerQuery::TFLJourneyPlannerTransactionError, this, scalp::Free1, scalp::Free2) ) );
	iTFLJourneyPlannerTransactionSource = new (ELeave) CHTTPTransactionSource;
	iDeflateDecompressor = CDeflateDecompressor::NewL();
	}

void CTFLJourneyPlannerQuery::TFLJourneyPlannerTransactionTimeoutL()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	SubmitL(*iStopOrigin, *iStopDestination, iTravelOption, iCallback.Unmanage());
	}

void CTFLJourneyPlannerQuery::Cancel()
	{
	if(iTFLJourneyPlannerTransaction != RHTTPTransaction())
		iTFLJourneyPlannerTransaction.Cancel();
	iTFLJourneyPlannerTransaction.Close();
	iTFLJourneyPlannerTransaction = RHTTPTransaction();
	}


// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
static void formUrlEncode(const TDesC8& aSource, RBuf8& aBuf)
	{
	static const TUint8 numToHex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
	for(const TUint8* p = aSource.Ptr() ; p != aSource.Ptr() + aSource.Length() ; ++p)
		{
		if(
				(*p >= 'a' && *p <= 'z')
				|| (*p >= 'A' && *p <= 'Z')
				|| (*p >= '0' && *p <= '9')
				|| (*p == '.') || (*p == '-') || (*p == '_')
				)
			{
			aBuf.Append(*p);			
			}
		else if (*p == ' ')
			{
			aBuf.Append('+');			
			}
		else
			{
			aBuf.Append('%');
			aBuf.Append( numToHex[*p >> 4] );
			aBuf.Append( numToHex[*p & 0x0F] );
			}
		}
	}



void CTFLJourneyPlannerQuery::SubmitL(const TDesC8& aStopOrigin, const TDesC8& aStopDestination, TInt aTravelOption, CTFLJourneyPlannerQuery::TCallbackType* aCallback)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iCallback = aCallback;
	delete iStopOrigin;
	delete iStopDestination;
	iStopOrigin = aStopOrigin.AllocL();
	iStopDestination = aStopDestination.AllocL();
	iTravelOption = aTravelOption;
	if(iTFLJourneyPlannerTransaction != RHTTPTransaction())
		{
		iTFLJourneyPlannerTransaction.Cancel();
		iTFLJourneyPlannerTransaction.Close();
		}
	
	TBuf8<2048> url;
	RBuf8 origin, destination;
	origin.CreateL(1024);
	CleanupClosePushL(origin);
	destination.CreateL(1024);
	CleanupClosePushL(destination);
	formUrlEncode(aStopOrigin, origin);
	formUrlEncode(aStopDestination, destination);
	url.Format(
		#include "tfl_journey_planner_query.txt"
			,&origin, &destination
			);
	CleanupStack::PopAndDestroy(2); // origin, destination
	iTFLJourneyPlannerTransaction = RHTTPSessionOpenTransactionL(iHTTPSession, url, *iTFLJourneyPlannerTransactionTarget, HTTP::EGET);
	RHTTPRequestSetHeaderL(iHTTPSession, iTFLJourneyPlannerTransaction.Request(), HTTP::EContentType, _L8("application/x-www-form-urlencoded; charset=utf-8"));
	RHTTPRequestSetHeaderL(iHTTPSession, iTFLJourneyPlannerTransaction.Request(), HTTP::EAcceptEncoding, _L8("gzip"));
	iTFLJourneyPlannerTransaction.SubmitL();
	iTFLJourneyPlannerTimeoutTarget->Cancel();
	iTFLJourneyPlannerTimeoutTimer.After(iTFLJourneyPlannerTimeoutTarget->iStatus, kTFLJourneyPlannerTimeout);
	iTFLJourneyPlannerTimeoutTarget->SetActive();
	}

TInt CTFLJourneyPlannerQuery::TFLJourneyPlannerTransactionError(TInt aError, RHTTPTransaction aTransaction)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	//REMOVED if(aTransaction != RHTTPTransaction()) aTransaction.Cancel();
	//REMOVED LCleanedupRef<RHTTPTransaction> cleanupTransaction(aTransaction);
	
	(*iCallback.Get())(this, aError, iTFLJourneyPlannerStatusCode, NULL);
	
	//if(aError == KErrNotReady || aError == KErrConnectionTerminated || aError == DndTimedOut) // not connected or connection lost
	//	doc->ConnectL();
	
	return KErrNone;
	}


void CTFLJourneyPlannerQuery::CloseConnectionL()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	Cancel();
	iHTTPSession.Close();
	}


void CTFLJourneyPlannerQuery::SetConnectionInfoL(const RConnection& aConnection, const RSocketServ& aSocketServ)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iHTTPSession.OpenL();
	RStringPool strP = iHTTPSession.StringPool();
	RHTTPConnectionInfo connInfo = iHTTPSession.ConnectionInfo();
	connInfo.SetPropertyL ( strP.StringF(HTTP::EHttpSocketServ, RHTTPSession::GetTable() ), THTTPHdrVal (aSocketServ.Handle()) );
	TInt connPtr = REINTERPRET_CAST(TInt, &aConnection);
	connInfo.SetPropertyL ( strP.StringF(HTTP::EHttpSocketConnection, RHTTPSession::GetTable() ), THTTPHdrVal (connPtr) );
	_LIT8(KUserAgent, "TFLJourneyPlanner 1.50 (iPod touch; iPhone OS 5.0.1; fr_FR)");
	_LIT8(KAccept, "*/*");
	_LIT8(KClose, "close");
	RHTTPSessionSetHeaderL(iHTTPSession, HTTP::EUserAgent, KUserAgent);
	RHTTPSessionSetHeaderL(iHTTPSession, HTTP::EConnection, KClose);
	}




void CTFLJourneyPlannerQuery::ReceivedTFLJourneyPlannerAnswerL(RHTTPTransaction aTransaction, THTTPEvent aEvent, TInt aLastError, TPtrC8 aBuf)
	{
	CSingaporeBusDocument* doc = (CSingaporeBusDocument*)CEikonEnv::Static()->EikAppUi()->Document();
	LCleanedupPtr<CJourneyPlannerOutput> output ( new (ELeave) CJourneyPlannerOutput );
	
	iTFLJourneyPlannerTimeoutTarget->Cancel();
	iTFLJourneyPlannerTransaction = RHTTPTransaction();
	TInt iTFLJourneyPlannerStatusCode = aTransaction.Response().StatusCode();
	if(aEvent.iStatus != THTTPEvent::ESucceeded)
		User::Leave(aLastError < 0 ? aLastError : KErrAbort);
	
	THTTPHdrVal contentEncodingHdrVal;
	TInt ret = aTransaction.Response().GetHeaderCollection().GetField(
					aTransaction.Session().StringPool().StringF(HTTP::EContentEncoding, RHTTPSession::GetTable()), 0,
					contentEncodingHdrVal
					);
	const TDesC8& contentEncoding( ret == KErrNone ? contentEncodingHdrVal.StrF().DesC() : KNullDesC8);
	TPtrC8 TFLJourneyPlannerAnswer = aBuf;
	if(ret == KErrNone && contentEncoding == _L8("gzip"))
		TFLJourneyPlannerAnswer.Set( iDeflateDecompressor->DecompressGZipL(aBuf) );

	/*{
	RFile file;
	file.Replace(doc->iFsData, _L("tflJourneyPlanner.xml"), EFileWrite);
	file.Write(TFLJourneyPlannerAnswer);
	file.Close();
	}*/
	
	output->iStatus = CJourneyPlannerOutput::ESuccess;
	{
	TPtrC8 buf = TFLJourneyPlannerAnswer;
	TPtrC8 itdTripRequestBuf = IrisExtractField(_L8("<itdTripRequest "), _L8("</itdTripRequest>"), buf);
	TPtrC8 itdMessage_error = IrisExtractField(_L8("<itdMessage type=\"error\""), _L8("</itdMessage>"), itdTripRequestBuf);
	if(itdMessage_error.Length() && itdMessage_error.Locate('>') != KErrNotFound)
		{
		TPtrC8 errorMessage = itdMessage_error.Mid(itdMessage_error.Locate('>') + 1);
		output->iStatus = CJourneyPlannerOutput::EError;
		output->iErrorMessage = narrowToWideL( errorMessage );
		}
	}
	
	if(output->iStatus == CJourneyPlannerOutput::ESuccess)
		{
		TPtrC8 buf (TFLJourneyPlannerAnswer);
		for(
				TPtrC8 itdRouteBuf = IrisExtractField(_L8("<itdRoute "), _L8("</itdRoute>"), buf);
				itdRouteBuf.Length();
				itdRouteBuf.Set( IrisExtractField(_L8("<itdRoute "), _L8("</itdRoute>"), buf)))
			{
			CJourneyPlannerOutput::CItinerary* itinerary = new (ELeave) CJourneyPlannerOutput::CItinerary;
			CleanupStack::PushL(itinerary);
			
			itinerary->iDuration = 0;
			itinerary->iDistance = 0;
			
			for(
					TPtrC8 itdPartialRouteBuf = IrisExtractField(_L8("<itdPartialRoute "), _L8("</itdPartialRoute>"), itdRouteBuf);
					itdPartialRouteBuf.Length();
					itdPartialRouteBuf.Set( IrisExtractField(_L8("<itdPartialRoute "), _L8("</itdPartialRoute>"), itdRouteBuf)))
				{
				CJourneyPlannerOutput::CItinerary::CLeg* leg = new (ELeave) CJourneyPlannerOutput::CItinerary::CLeg;
				CleanupStack::PushL(leg);
				
				{
				TPtrC8 tempItdPartialRouteBuf = itdPartialRouteBuf;
				TPtrC8 timeMinute = IrisExtractField(_L8("timeMinute=\""), _L8("\""), tempItdPartialRouteBuf);
				TPtrC8 distance = IrisExtractField(_L8("distance=\""), _L8("\""), tempItdPartialRouteBuf);
				{
				TInt time_min;
				TLex8 lex(timeMinute); lex.Val(time_min);
				leg->iDuration = time_min*60;
				itinerary->iDuration += leg->iDuration;
				}
				{
				TInt temp;
				TLex8 lex(distance); lex.Val(temp);
				leg->iDistance = temp;
				itinerary->iDistance += leg->iDistance;
				}
				}
				
				TPtrC8 departure_name, arrival_name;
				{
				TPtrC8 tempItdPartialRouteBuf = itdPartialRouteBuf;
				for(
						TPtrC8 itdPointBuf = IrisExtractField(_L8("<itdPoint "), _L8("</itdPoint>"), tempItdPartialRouteBuf);
						itdPointBuf.Length();
						itdPointBuf.Set( IrisExtractField(_L8("<itdPoint "), _L8("</itdPoint>"), tempItdPartialRouteBuf)))
					{
					TPtrC8 name = IrisExtractField(_L8("name=\""), _L8("\""), itdPointBuf);
					TPtrC8 usage = IrisExtractField(_L8("usage=\""), _L8("\""), itdPointBuf);
					if(usage == _L8("departure"))
						departure_name.Set(name);
					else if(usage == _L8("arrival"))
						arrival_name.Set(name);
					}
				}
				
				if(departure_name.Length())
					leg->iFromLocation = narrowToWideL(departure_name);
				if(arrival_name.Length())
					leg->iToLocation = narrowToWideL(arrival_name);

				{
				TPtrC8 tempItdPartialRouteBuf = itdPartialRouteBuf;
				TPtrC8 itdMeansOfTransport = IrisExtractField(_L8("<itdMeansOfTransport "), _L8("</itdMeansOfTransport>"), tempItdPartialRouteBuf);
				TPtrC8 name = IrisExtractField(_L8("name=\""), _L8("\""), itdMeansOfTransport);
				TPtrC8 symbol = IrisExtractField(_L8("symbol=\""), _L8("\""), itdMeansOfTransport);
				TPtrC8 productName = IrisExtractField(_L8("productName=\""), _L8("\""), itdMeansOfTransport);
				
				leg->iServiceDesc = narrowToWideL( name );
				
				if(productName == _L8("Fussweg"))
					leg->iMode = CJourneyPlannerOutput::CItinerary::CLeg::WALK;
				else if(productName == _L8("Bus"))
					leg->iMode = CJourneyPlannerOutput::CItinerary::CLeg::BUS;
				else
					leg->iMode = CJourneyPlannerOutput::CItinerary::CLeg::OTHER;

				
				for(TInt serviceDirection = 1 ; serviceDirection <= 2 ; ++serviceDirection)
				{
				TBuf8<15> serviceKeyUpper;
				serviceKeyUpper.Format(_L8("%S %d"), &symbol, serviceDirection);
				serviceKeyUpper.UpperCase();
				TService target = { serviceKeyUpper };
				TInt ret = doc->iServicesSortedByServiceKey.SpecificFindInOrder(&target, TLinearOrder<const TService*>(&CSingaporeBusDocument::TLinearOrder_Service), EArrayFindMode_Any);
				if(ret != KErrNotFound)
					{
					const TService* service = doc->iServicesSortedByServiceKey[ret];
					TInt i;
					for(i = service->iStationsBeg ; i < service->iStationsEnd ; ++i)
						{
						TPtrC8 stationNumber = doc->iServiceStations[i];
						TStation target = { stationNumber };
						TInt ret = doc->iStationsSortedByStationNumber.SpecificFindInOrder(&target, TLinearOrder<const TStation*>(&CSingaporeBusDocument::TLinearOrder_Station), EArrayFindMode_Any);
						if(ret != KErrNotFound)
							{
							const TStation* station = doc->iStationsSortedByStationNumber[ret];
							if( departure_name.Find(station->iAddress2) != KErrNotFound )
								{
								leg->iFromStation = station;
								leg->iService = service;
								}
							else if( arrival_name.Find(station->iAddress2) != KErrNotFound )
								{
								leg->iToStation = station;
								leg->iService = service;
								}
							}
						}
					}
				}
				}
				
												
				itinerary->iLegs.AppendL(leg);
				CleanupStack::Pop(leg);
				}
			
			
			
			output->iItineraries.AppendL(itinerary);
			CleanupStack::Pop(itinerary);
			}
		}
	
	(*iCallback.Get())(this, aLastError, iTFLJourneyPlannerStatusCode, output.Unmanage());
	
	aTransaction.Cancel();
	aTransaction.Close(); // note: if a panic is raised before reaching this line, MHFRunError will close the transaction
	}


TBool CTFLJourneyPlannerQuery::Pending() const
	{
	return (iTFLJourneyPlannerTransaction != RHTTPTransaction());
	}

